﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Collections.Concurrent;
using System.Diagnostics;
using ShredTheLog.Api.DataContracts;
using ShredTheLog.Api;

namespace ShredTheLog.Translation
{
    public sealed class CachingTranslator : ITranslatorBase
    {
        private const int MaxCacheSize = 10000;
        private ITranslatorBase _innerTranslator;
        private ConcurrentDictionary<int, CacheItem> _cache = new ConcurrentDictionary<int, CacheItem>();

        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(_innerTranslator != null);
            Contract.Invariant(_cache != null);
        }

        public CachingTranslator(ITranslatorBase innerTranslator)
        {
            Contract.Requires(innerTranslator != null);

            _innerTranslator = innerTranslator;
        }

        public IObservable<TranslateResponse> Translate(TranslateRequest translateRequest, bool ignoreErrors)
        {
            int hash = translateRequest.GetHashCode();
            CacheItem cacheItem;
            if (_cache.TryGetValue(hash, out cacheItem) && Object.Equals(cacheItem.Request, translateRequest))
            {
                var result = Observable.Return(new TranslateResponse(cacheItem.Response.Text, cacheItem.Response.DetectedSourceLanguage, translateRequest.Context));
                Contract.Assume(result != null);
                return result;
            }
            else
            {
                // Чистим кэш от старых ссылок
                int n = _cache.Count - MaxCacheSize;
                if (n > 0)
                {
                    _cache
                        .Select(i => new { TimeStamp = i.Value.TimeStamp, Key = i.Key })
                        .OrderBy(i => i.TimeStamp)
                        .Take(n)
                        .Select(i => i.Key)
                        .ToArray()
                        .ForEach(i => _cache.TryRemove(i, out cacheItem));
                }

                var result =
                    _innerTranslator.Translate(translateRequest, ignoreErrors)
                    .Do(response => _cache.TryAdd(hash, new CacheItem(translateRequest, response)));
                Contract.Assume(result != null);
                return result;
            }

        }

        public IObservable<DetectResponse> Detect(string text)
        {
            return _innerTranslator.Detect(text);
        }

        public IEnumerable<Language> SupportedLanguages { get { return _innerTranslator.SupportedLanguages; } }

        sealed class CacheItem
        {
            public DateTime TimeStamp { get; private set; }
            public TranslateRequest Request { get; private set; }
            private TranslateResponse _response;
            public TranslateResponse Response { get { TimeStamp = DateTime.Now; return _response; } private set { _response = value; } }

            public CacheItem(TranslateRequest request, TranslateResponse response)
            {
                Contract.Requires(request != null);
                Contract.Requires(response != null);

                TimeStamp = DateTime.Now;
                Request = request;
                Response = response;
            }
        }
    }
}
